Skip to content

feat(ui): crisp fades further + fix two latent bugs from repo audit#46

Open
The-Big-Mini wants to merge 11 commits into
developfrom
claude/fade-crisp-and-audit
Open

feat(ui): crisp fades further + fix two latent bugs from repo audit#46
The-Big-Mini wants to merge 11 commits into
developfrom
claude/fade-crisp-and-audit

Conversation

@The-Big-Mini

Copy link
Copy Markdown
Owner

1. Crisp fade animations (follow-up)

Tab switching still read slightly soft, so every timed fade dropped one more notch:

Fade Before After
Tab cross-dissolve (TabSwitchFadeAnimator) 0.15s ease-out 0.1s ease-out
Launch intro spring 0.25s / damp 0.9 0.2s / damp 0.9
Updates progress-hide / install-button cross-fade 0.2s ease-out 0.15s
What's New reveal / expand 0.2s ease-out 0.15s

Curves and spring physics unchanged.

2. Repo audit — two latent bugs fixed

Fanned out a 4-area audit (operations, AltStoreCore, SwiftUI screens, SideStore/Widget/Backup/Shared). Most candidates were false positives or inherited-intentional; two real, safe fixes landed:

  • ConsoleLogger.readHandler — bind a strong self before locking shutdownLock. The old order had a window where the logger could deallocate after locking; the deferred self?.unlock() then no-ops through the nil weak ref, leaving the lock held and deadlocking deinit's stopCapturing().
  • AltBackup operationDidFinish — fire the local completion notification + log when the response URL fails to construct, matching the two guards above it. Previously returned silently → a backgrounded user got no result.

Audited and deliberately NOT changed (verified on disk)

  • MyAppsView image-picker / confirmation dialogs — already capture app correctly before the dismiss-binding nils it.
  • AppManager.bundleIdentifierperformAndWait always runs the block; no nil-unwrap.
  • DatabaseManager orphan-delete sourceURL IN predicate — CoreData URL predicates work (same pattern in LaunchViewController).
  • Widget allAppsExpired / first-vs-last expiring — inherited AltStore timeline-relevance design, intentional.
  • Source17To17_1 migration, BackgroundRefreshAppsOperation Unmanaged keep-alive — regression-sensitive / already-guarded; not touched blind.
  • Shared/Connections/XPCConnection — dead code (never instantiated).

Notes

Animation timing is best judged on-device. No logic touched beyond the two audit fixes.

🤖 Generated with Claude Code

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx


Generated by Claude Code

claude added 2 commits June 22, 2026 11:47
Tab switch was still reading slightly soft, so shorten every timed fade one
more step: tab cross-dissolve 0.15s → 0.1s, launch intro spring 0.25s → 0.2s,
Updates progress-hide / install-button cross-fade and What's New reveal/expand
0.2s → 0.15s. Curves (ease-out) and spring physics unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
ConsoleLogger.readHandler: bind a strong `self` *before* locking shutdownLock.
The previous order (`self?.lock()` then `guard let self`) had a window where
the logger could deallocate after the lock was taken — the deferred
`self?.unlock()` would no-op through the now-nil weak reference, leaving the
lock held and deadlocking deinit's stopCapturing(), which also locks it.

AltBackup operationDidFinish: fire the local completion notification (and log)
when the response URL fails to construct, matching the two guards above it.
Previously this path returned silently, so a backgrounded user got no result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
github-actions Bot added a commit that referenced this pull request Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Download the artifacts for this pull request (nightly.link):

@github-actions

Copy link
Copy Markdown
Contributor

Builds for this Pull Request are available at
MiniStore-0.6.5-pr.46.4f82f04-dSYMs
MiniStore-0.6.5-pr.46.4f82f04
build-logs-0.6.5-pr.46.4f82f04
Have a nice day.

Screenshots were rendered with .scaledToFit() inside a ZStack whose
frame was pre-sized to the metadata aspect ratio. When actual image
pixels differed from the metadata ratio, secondarySystemBackground
showed through as left/right bars. .scaledToFill() with the existing
.clipShape(RoundedRectangle) clips the overflow cleanly.

Applies to both the detail carousel (DetailScreenshotView) and the
fullscreen preview carousel (PreviewScreenshotView).

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
github-actions Bot added a commit that referenced this pull request Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Builds for this Pull Request are available at
MiniStore-0.6.5-pr.46.c1f739c-dSYMs
MiniStore-0.6.5-pr.46.c1f739c
build-logs-0.6.5-pr.46.c1f739c
Have a nice day.

claude added 4 commits June 22, 2026 19:30
verifyHash loaded the entire IPA into memory via Data(contentsOf:) before
hashing. For large apps (hundreds of MB) this spiked resident memory in the
middle of an install, on a device that may already be memory-constrained.

Use .mappedIfSafe so the file is memory-mapped instead. SHA256 reads the
buffer sequentially, so pages are touched once and reclaimed by the kernel
under pressure — same hash result, far smaller memory high-water mark.

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
The Dolphin special-case rewrites the downloaded app's CFBundleIdentifier
so prior installs keep updating in place. The NSDictionary.write(to:) Bool
result was discarded, so a write failure silently shipped the app with the
wrong bundle identifier and no trace of why in-place updates broke.

Check the return and log on failure. The install still proceeds (the app
works either way), but the failure is now diagnosable.

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
…e-fire

BackgroundRefreshAppsOperation.finish(_:) overrides the base to run two side
effects after super.finish(): scheduleFinishedRefreshingNotification (which
persists a RefreshAttempt row + arms a local notification) and
stopListeningForRunningApps. The base finish() is idempotent via its own
isFinished guard, but these side effects are not — a second finish call would
write a duplicate RefreshAttempt and re-arm the notification cascade.

No current code path double-finishes, so this is latent, not an active bug.
Add a `guard !self.isFinished` before super.finish (which flips isFinished)
so the side effects run exactly once, matching the operation finish contract.

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
Three backup paths failed silently:

- backupExists(for:) passed an out-error to NSFileCoordinator but never
  inspected it. A transient coordination failure read as "no backup",
  hiding a real backup and mislabelling the Restore control. Now logged.

- restorePreviousBackup(for:) and exportBackup(for:) returned silently when
  backupDirectoryURL(for:) was nil (missing app group) — the user tapped a
  menu action and nothing happened, with no log or UI feedback. Both now
  log and show a toast. restorePreviousBackup's copy-failure catch also
  gained a toast so the user sees the failure instead of a dead tap.

Per the "never swallow errors" rule: log domain + code, set a visible state.

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
github-actions Bot added a commit that referenced this pull request Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Builds for this Pull Request are available at
MiniStore-0.6.5-pr.46.a299940-dSYMs
MiniStore-0.6.5-pr.46.a299940
build-logs-0.6.5-pr.46.a299940
Have a nice day.

performBackup was already safe: it copies into a temp directory then
atomically swaps with replaceItemAt. restoreBackup had no such protection —
it wrote each directory directly into the live container using
copyDirectoryContents (remove-then-copy per item). A failure midway left the
live Documents/Library/app-group containers in a half-overwritten state with
no path back to the original data.

Fix: before touching any live directory, snapshot each one into a UUID temp
directory in FileManager.temporaryDirectory. If snapshotting fails, we abort
before modifying live data. During restore, any failure triggers rollback of
all previously applied targets (and the partially-applied current one) using
the snapshots. Rollback failures are logged but don't mask the original error.
Snapshots are cleaned up on both success and failure.

This mirrors the intent of the backup's own temp+swap pattern: the live app
data is never left in an inconsistent state.

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
github-actions Bot added a commit that referenced this pull request Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Builds for this Pull Request are available at
MiniStore-0.6.5-pr.46.315aa8a-dSYMs
MiniStore-0.6.5-pr.46.315aa8a
build-logs-0.6.5-pr.46.315aa8a
Have a nice day.

claude added 2 commits June 22, 2026 20:46
The earlier switch to .scaledToFill() removed the letterbox bars but let the
enlarged image overflow its frame — .clipShape on the outer ZStack wasn't
cropping the horizontal overflow during scroll, so screenshots bled past the
rounded card and "clipped off" the page edges.

Give the Image its own .frame(width:height:) + .clipped() so it's cropped
tight to the card before the rounded-corner clipShape. Applies to both the
detail carousel and the fullscreen preview carousel.

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
github-actions Bot added a commit that referenced this pull request Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Builds for this Pull Request are available at
MiniStore-0.6.5-pr.46.f74ec8b-dSYMs
MiniStore-0.6.5-pr.46.f74ec8b
build-logs-0.6.5-pr.46.f74ec8b
Have a nice day.

… backupExists

backupExists was called on the main thread from SwiftUI body evaluation
for every My Apps row. NSFileCoordinator.coordinate(readingItemAt:) is a
synchronous blocking IPC call — if AltBackup still holds a write-coordination
lock on the backup directory after completing a backup, MiniStore's main thread
blocks waiting for the lock, producing the "instant freeze" on restore.

Direct FileManager.fileExists is safe here: the backup URL is constructed
locally (no symlink/redirect resolution needed), and the directory is only
written by AltBackup, which is a separate process.

https://claude.ai/code/session_017rnLQsvspk1uZDExv3rUxx
github-actions Bot added a commit that referenced this pull request Jun 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Builds for this Pull Request are available at
MiniStore-0.6.5-pr.46.15fc36b-dSYMs
MiniStore-0.6.5-pr.46.15fc36b
build-logs-0.6.5-pr.46.15fc36b
Have a nice day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants